﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media.Imaging;
using System.Diagnostics;

namespace Rabbit.Controls
{
    public class RichTextBoxEx : RichTextBox
    {

        #region dependency property, check whether there is content in rich text box
        public bool HasContent
        {
            get { return (bool)GetValue(HasContentProperty); }
            set { SetValue(HasContentProperty, value); }
        }

        // Using a DependencyProperty as the backing store for HasContent.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HasContentProperty =
            DependencyProperty.Register("HasContent", typeof(bool), typeof(RichTextBoxEx), new UIPropertyMetadata(false));
        #endregion

        private List<Image> imageList = new List<Image>();

        public List<string> ContentAssistSource = new List<string>();
        public List<char> ContentAssistTriggers = new List<char>();
        
        #region constructure
        public RichTextBoxEx()
        {
            AllowDrop = true;
            this.Loaded += new RoutedEventHandler(RichTextBoxEx_Loaded);
        }

        void RichTextBoxEx_Loaded(object sender, RoutedEventArgs e)
        {
            (this.Parent as Grid).Children.Add(AssistListBox);
            AssistListBox.Height = 100;
            AssistListBox.Width = 100;
            AssistListBox.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
            AssistListBox.VerticalAlignment = System.Windows.VerticalAlignment.Top;
            AssistListBox.Visibility = System.Windows.Visibility.Collapsed;
        }
        #endregion

        #region check richtextbox's document.blocks is available
        private void CheckMyDocumentAvailable()
        {
            if (this.Document == null)
            {
                this.Document = new System.Windows.Documents.FlowDocument();
            }
            if (Document.Blocks.Count == 0)
            {
                Paragraph para = new Paragraph();
                Document.Blocks.Add(para);
            }
        } 
        #endregion

        #region Infotext, which is used as "hint text"
        public string InfoText
        {
            get { return (string)GetValue(InfoTextProperty); }
            set { SetValue(InfoTextProperty, value); }
        }

        // Using a DependencyProperty as the backing store for InfoText.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InfoTextProperty =
            DependencyProperty.Register("InfoText",
            typeof(string),
            typeof(RichTextBoxEx),
            new UIPropertyMetadata(string.Empty, new PropertyChangedCallback(OnInfoTextChanged)));

        static void OnInfoTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            RichTextBoxEx rtb = sender as RichTextBoxEx;
            if (rtb.Document == null)
            {
                rtb.Document = new FlowDocument();
            }
            if (rtb.Document.Blocks.Count == 0)
            {
                Paragraph para = new Paragraph();
                rtb.Document.Blocks.Add(para);
            }
            if (string.IsNullOrEmpty(e.NewValue.ToString()))
            {
                rtb.Document.Blocks.Clear();
            }
            else
            {
                rtb.Selection.Start.DeleteTextInRun(e.OldValue.ToString().Count());
                rtb.Selection.Start.InsertTextInRun(e.NewValue.ToString());
            }
        }
        #endregion

        #region Focus changed
        protected override void OnGotFocus(RoutedEventArgs e)
        {
            base.OnGotFocus(e);
            if (this.realText == this.InfoText)
            {
                Document.Blocks.Clear();
            }

            HasContent = true;
        }

        protected override void OnLostFocus(RoutedEventArgs e)
        {
            base.OnLostFocus(e);
            if (this.realText == "" && this.GetImages().Count == 0)
            {
                Document.Blocks.Clear();
                OnInfoTextChanged(this, new DependencyPropertyChangedEventArgs(InfoTextProperty, InfoText, InfoText));
                HasContent = false;
            }
        }
        #endregion

        #region Insert Image
        //TODO to be refined
        public void InsertImage(Image image)
        {
            Focus();
            CheckMyDocumentAvailable();
            image.MaxWidth = 250;
            image.Stretch = System.Windows.Media.Stretch.None;
            (Document.Blocks.FirstBlock as Paragraph).Inlines.Add(Environment.NewLine);
            (Document.Blocks.FirstBlock as Paragraph).Inlines.Add(image);
            CaretPosition = Document.ContentStart;
        }

        //TODO to be refined
        public void InsertImage(string path)
        {
            //get the image size
           BitmapFrame bf=  BitmapFrame.Create(new Uri(path), BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
            BitmapImage bi = new BitmapImage();
            bi.BeginInit();
            bi.StreamSource = File.OpenRead(path);
            bi.DecodePixelWidth = bf.PixelWidth>=600?600:bf.PixelWidth;
            bi.EndInit();
             
            Image image = new Image();  
            image.Source = bi;
            InsertImage(image);
        }
#endregion

        #region Insert Text
        //TODO we need to do more here, such as the set the correct caretposition after inserted text
        public void InsertText(string text)
        {
            Focus();
            using (this.DeclareChangeBlock())
            {
                TextPointer pointer = Selection.Start;
                pointer.InsertTextInRun(text);
            }
            Focus();
        }
#endregion

        #region Clear Conten
        public void ClearContent()
        {
            foreach (var item in GetImages())
            {
                if ((item.Source as BitmapImage).StreamSource != null)
                {
                    (item.Source as BitmapImage).StreamSource.Close();
                }
            }
            Document.Blocks.Clear();
            HasContent = false;
            OnInfoTextChanged(this, new DependencyPropertyChangedEventArgs(InfoTextProperty, InfoText, InfoText));
            GC.Collect(2, GCCollectionMode.Forced);
        }
	#endregion

        #region Get Images List
        public List<Image> GetImages()
        {
            List<Image> imageList = new List<Image>();
            TextPointer position = Document.ContentStart;
            TextRange tx = new TextRange(Document.ContentStart, Document.ContentEnd);

            while (position != null)
            {
                if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.EmbeddedElement)
                {
                    DependencyObject dp = position.GetAdjacentElement(LogicalDirection.Forward);
                    if (dp.GetType() == typeof(Image))
                    {
                        imageList.Add(dp as Image);
                    }
                }
                position = position.GetNextContextPosition(LogicalDirection.Forward);
            }

            return imageList;
        }
        #endregion

        #region Text
        private string realText
        {
            get
            {
                TextRange tr = new TextRange(Document.ContentStart, Document.ContentEnd);
                return tr.Text.Replace("\r\n", "");
            }
        }

        public string Text
        {
            get
            {
                TextRange tr = new TextRange(Document.ContentStart, Document.ContentEnd);
                string text = tr.Text.Replace("\r\n", "");
                if (text == InfoText)
                {
                    return "";
                }
                return tr.Text.Replace("\r\n", "");
            }
        } 
        #endregion

        #region Drag and drop
        protected override void OnDragEnter(DragEventArgs e)
        {
            base.OnDragEnter(e);

            if (e.Data.GetDataPresent("FileNameW"))
            {
                e.Effects = DragDropEffects.All;
                string imgSource = ((string[])e.Data.GetData(DataFormats.FileDrop))[0];
                if (File.Exists(imgSource))
                {
                    FileInfo fi = new FileInfo(imgSource);
                    string allowedExtension = ".jpg,.jpeg,.png,.gif,.bmp";
                    if (allowedExtension.Split(',').ToList().Exists(m => m == fi.Extension.ToLower()))
                    {
                        DataObject data = new DataObject();
                        data.SetText(imgSource);
                        DragDrop.DoDragDrop(this, data, DragDropEffects.All);
                    }
                }
            }
        }

        protected override void OnDrop(DragEventArgs e)
        {
            base.OnDrop(e);
            if (e.Data.GetDataPresent(DataFormats.UnicodeText))
            {
                try
                {
                    string picturePath = e.Data.GetData(DataFormats.UnicodeText).ToString();
                    InsertImage(picturePath);
                }
                catch
                {

                }
            }
        } 
        #endregion

        #region Content Assist

        private bool IsAssistKeyPressed = false;
        private string lastWord = "";
        protected override void OnKeyUp(System.Windows.Input.KeyEventArgs e)
        {
            //check whether the assist key is pressed
            if (IsAssistKeyPressed == false )
            {
                for (int i = 0; i < ContentAssistTriggers.Count; i++)
                {
                    int a=ContentAssistTriggers[i];
                    int b=(int)(e.Key.ToString()[0]);
                    if (a==b)
                    {
                        IsAssistKeyPressed = true;
                        base.OnKeyUp(e);
                        return;
                    }
                }
            }

            if (IsAssistKeyPressed)
            {
                //flush the selected item
                if ((char)e.Key == ' ')
                {
                    InterSelectedText();
                    IsAssistKeyPressed = false;
                    lastWord = "";
                }
                else
                {
                    lastWord += (char)e.Key;
                }
            }

            if (!IsAssistKeyPressed)
            {
                base.OnKeyUp(e);
                return;
            }

            //filter the assist item source, and show the assist list box

            if (AssistListBox.Visibility == System.Windows.Visibility.Collapsed)
            {
               AssistListBox.Visibility = System.Windows.Visibility.Visible;
               AssistListBox.ItemsSource = ContentAssistSource;//.FindAll(s => s.StartsWith(lastWord));
            }

            Rect rect = this.CaretPosition.GetCharacterRect(LogicalDirection.Forward);
            Debug.Print("rect.Location.X:" +rect.Location.X);
            Debug.Print("rect.Location.Y:" + rect.Location.Y);
            AssistListBox.SetCurrentValue(ListBox.MarginProperty, new Thickness(rect.X, rect.Y + 20, 0, 0));
            //AssistListBox.TranslatePoint(rect.Location, this);
        }


        private void InterSelectedText()
        {
            MessageBox.Show("selected text:" + AssistListBox.SelectedItem.ToString());
        }
        private ListBox AssistListBox =new ListBox();
        #endregion

    }

}
